using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using Status = javax.transaction.Status;
	using Synchronization = javax.transaction.Synchronization;
	using SystemException = javax.transaction.SystemException;
	using Transaction = javax.transaction.Transaction;
	using TransactionManager = javax.transaction.TransactionManager;

	using NotInTransactionException = org.neo4j.graphdb.NotInTransactionException;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using LockManager = org.neo4j.kernel.impl.transaction.LockManager;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;
	using IntArray = org.neo4j.kernel.impl.util.IntArray;

///
/// <summary> * Manages object version diffs and locks for each transaction. </summary>
/// 
	public class LockReleaser
	{
		private static Logger log = Logger.getLogger(typeof(LockReleaser).Name);

		private readonly ArrayMap<Transaction, List<LockElement>> lockMap = new ArrayMap<Transaction, List<LockElement>>(5, true, true);
		private readonly ArrayMap<Transaction, NeoPrimitiveElement> cowMap = new ArrayMap<Transaction, NeoPrimitiveElement>(5, true, true);

		private NodeManager nodeManager;
		private readonly LockManager lockManager;
		private readonly TransactionManager transactionManager;
		private PropertyIndexManager propertyIndexManager;

		private class NeoPrimitiveElement
		{
			internal NeoPrimitiveElement()
			{
			}

			internal readonly ArrayMap<int, CowNodeElement> nodes = new ArrayMap<int, CowNodeElement>();
			internal readonly ArrayMap<int, CowRelElement> relationships = new ArrayMap<int, CowRelElement>();
		}

		private class CowNodeElement
		{
			internal CowNodeElement()
			{

			}

			internal ArrayMap<string, IntArray> relationshipAddMap = null;
			internal ArrayMap<string, IntArray> relationshipRemoveMap = null;
			internal ArrayMap<int, PropertyData> propertyAddMap = null;
			internal ArrayMap<int, PropertyData> propertyRemoveMap = null;
		}

		private class CowRelElement
		{
			internal CowRelElement()
			{

			}

			internal ArrayMap<int, PropertyData> propertyAddMap = null;
			internal ArrayMap<int, PropertyData> propertyRemoveMap = null;
		}

		public LockReleaser(LockManager lockManager, TransactionManager transactionManager)
		{
			this.lockManager = lockManager;
			this.transactionManager = transactionManager;
		}

		internal virtual void setNodeManager(NodeManager nodeManager)
		{
			this.nodeManager = nodeManager;
		}

		internal virtual void setPropertyIndexManager(PropertyIndexManager propertyIndexManager)
		{
			this.propertyIndexManager = propertyIndexManager;
		}

		private class LockElement
		{
			internal object resource;
			internal LockType lockType;

			internal LockElement(object resource, LockType type)
			{
				this.resource = resource;
				this.lockType = type;
			}
		}

///    
///     <summary> * Invoking this method with no transaction running will cause the lock to 
///     * be released right away.
///     *  </summary>
///     * <param name="resource">
///     *            the resource on which the lock is taken </param>
///     * <param name="type">
///     *            type of lock (READ or WRITE) </param>
///     * <exception cref="NotInTransactionException"> </exception>
///     
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void addLockToTransaction(Object resource, LockType type) throws NotInTransactionException
		public virtual void addLockToTransaction(object resource, LockType type)
		{
			Transaction tx = getTransaction();
			List<LockElement> lockElements = lockMap.get(tx);
			if (lockElements != null)
			{
				lockElements.Add(new LockElement(resource, type));
			}
			else
			{
				if (tx == null)
				{
				// no transaction we release lock right away
					if (type == LockType.WRITE)
					{
						lockManager.releaseWriteLock(resource);
					}
					else if (type == LockType.READ)
					{
						lockManager.releaseReadLock(resource);
					}
					return;
				}
				lockElements = new List<LockElement>();
				lockMap.put(tx, lockElements);
				lockElements.Add(new LockElement(resource, type));
			// we have to have a syncrhonization hook for read only transaction,
			// write locks can be taken in read only transactions (ex: 
			// transactions that peform write operations that cancel each other
			// out). This sync hook will only release locks if they exist and 
			// tx was read only
				try
				{
					tx.registerSynchronization(new ReadOnlyTxReleaser(tx));
				}
				catch (Exception e)
				{
					throw new TransactionFailureException("Failed to register lock release synchronization hook", e);
				}
			}
		}

		private Transaction getTransaction()
		{
			try
			{
				return transactionManager.getTransaction();
			}
			catch (SystemException e)
			{
				throw new TransactionFailureException("Failed to get current transaction.", e);
			}
		}

		public virtual IntArray getCowRelationshipRemoveMap(NodeImpl node, string type)
		{
			NeoPrimitiveElement primitiveElement = cowMap.get(getTransaction());
			if (primitiveElement != null)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(node.id);
				if (element != null && element.relationshipRemoveMap != null)
				{
					return element.relationshipRemoveMap.get(type);
				}
			}
			return null;
		}

		public virtual IntArray getCowRelationshipRemoveMap(NodeImpl node, string type, bool create)
		{
			if (!create)
			{
				return getCowRelationshipRemoveMap(node, type);
			}
			NeoPrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
			ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
			CowNodeElement element = cowElements.get(node.id);
			if (element == null)
			{
				element = new CowNodeElement();
				cowElements.put(node.id, element);
			}
			if (element.relationshipRemoveMap == null)
			{
				element.relationshipRemoveMap = new ArrayMap<string, IntArray>();
			}
			IntArray @set = element.relationshipRemoveMap.get(type);
			if (@set == null)
			{
				@set = new IntArray();
				element.relationshipRemoveMap.put(type, @set);
			}
			return @set;
		}

		public virtual ArrayMap<string, IntArray> getCowRelationshipAddMap(NodeImpl node)
		{
			NeoPrimitiveElement primitiveElement = cowMap.get(getTransaction());
			if (primitiveElement != null)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(node.id);
				if (element != null)
				{
					return element.relationshipAddMap;
				}
			}
			return null;
		}

		public virtual IntArray getCowRelationshipAddMap(NodeImpl node, string type)
		{
			NeoPrimitiveElement primitiveElement = cowMap.get(getTransaction());
			if (primitiveElement != null)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(node.id);
				if (element != null && element.relationshipAddMap != null)
				{
					return element.relationshipAddMap.get(type);
				}
			}
			return null;
		}

		public virtual IntArray getCowRelationshipAddMap(NodeImpl node, string type, bool create)
		{
			if (!create)
			{
				return getCowRelationshipRemoveMap(node, type);
			}
			NeoPrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
			ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
			CowNodeElement element = cowElements.get(node.id);
			if (element == null)
			{
				element = new CowNodeElement();
				cowElements.put(node.id, element);
			}
			if (element.relationshipAddMap == null)
			{
				element.relationshipAddMap = new ArrayMap<string, IntArray>();
			}
			IntArray @set = element.relationshipAddMap.get(type);
			if (@set == null)
			{
				@set = new IntArray();
				element.relationshipAddMap.put(type, @set);
			}
			return @set;
		}

		public virtual void commit()
		{
			Transaction tx = getTransaction();
		// propertyIndex
			propertyIndexManager.commit(tx);
			releaseCows(tx, Status.STATUS_COMMITTED);
			releaseLocks(tx);
		}

		public virtual void rollback()
		{
			Transaction tx = getTransaction();
		// propertyIndex
			propertyIndexManager.rollback(tx);
			releaseCows(tx, Status.STATUS_ROLLEDBACK);
			releaseLocks(tx);
		}

		internal virtual void releaseLocks(Transaction tx)
		{
			List<LockElement> lockElements = lockMap.Remove(tx);
			if (lockElements != null)
			{
				foreach (LockElement lockElement in lockElements)
				{
					try
					{
						if (lockElement.lockType == LockType.READ)
						{
							lockManager.releaseReadLock(lockElement.resource);
						}
						else if (lockElement.lockType == LockType.WRITE)
						{
							lockManager.releaseWriteLock(lockElement.resource);
						}
					}
					catch (Exception e)
					{
						e.printStackTrace();
						log.severe("Unable to release lock[" + lockElement.lockType + "] on resource[" + lockElement.resource + "]");
					}
				}
			}
		}

		internal virtual void releaseCows(Transaction cowTxId, int param)
		{
			NeoPrimitiveElement element = cowMap.Remove(cowTxId);
			if (element == null)
			{
				return;
			}
			ArrayMap<int, CowNodeElement> cowNodeElements = element.nodes;
			Set<KeyValuePair<int, CowNodeElement>> nodeEntrySet = cowNodeElements.entrySet();
			foreach (KeyValuePair<int, CowNodeElement> entry in nodeEntrySet)
			{
				NodeImpl node = nodeManager.getNodeIfCached(entry.Key);
				if (node != null)
				{
					CowNodeElement nodeElement = entry.Value;
					if (param == Status.STATUS_COMMITTED)
					{
						node.commitRelationshipMaps(nodeElement.relationshipAddMap, nodeElement.relationshipRemoveMap);
						node.commitPropertyMaps(nodeElement.propertyAddMap, nodeElement.propertyRemoveMap);
					}
					else if (param != Status.STATUS_ROLLEDBACK)
					{
						throw new TransactionFailureException("Unknown transaction status: " + param);
					}
				}
			}
			ArrayMap<int, CowRelElement> cowRelElements = element.relationships;
			Set<KeyValuePair<int, CowRelElement>> relEntrySet = cowRelElements.entrySet();
			foreach (KeyValuePair<int, CowRelElement> entry in relEntrySet)
			{
				RelationshipImpl rel = nodeManager.getRelIfCached(entry.Key);
				if (rel != null)
				{
					CowRelElement relElement = entry.Value;
					if (param == Status.STATUS_COMMITTED)
					{
						rel.commitPropertyMaps(relElement.propertyAddMap, relElement.propertyRemoveMap);
					}
					else if (param != Status.STATUS_ROLLEDBACK)
					{
						throw new TransactionFailureException("Unknown transaction status: " + param);
					}
				}
			}
			cowMap.Remove(cowTxId);
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void dumpLocks()
		{
			Console.Write("Locks held: ");
//JAVA TO VB & C# CONVERTER TODO TASK: Java wildcard generics are not converted to .NET:
//ORIGINAL LINE: java.util.IEnumerator <?> itr = lockMap.keySet().iterator();
			java.util.IEnumerator <?> itr = lockMap.Keys.GetEnumerator();
			if (!itr.MoveNext())
			{
				Console.WriteLine("NONE");
			}
			else
			{
				Console.WriteLine();
			}
			while (itr.MoveNext())
			{
				Transaction transaction = (Transaction) itr.Current;
				Console.WriteLine("" + transaction + "->" + lockMap.get(transaction).size());
			}
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyRemoveMap(NeoPrimitive primitive)
		{
			NeoPrimitiveElement primitiveElement = cowMap.get(getTransaction());
			if (primitiveElement != null && primitive is NodeImpl)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(primitive.id);
				if (element != null)
				{
					return element.propertyRemoveMap;
				}
			}
			else if (primitiveElement != null && primitive is RelationshipImpl)
			{
				ArrayMap<int, CowRelElement> cowElements = primitiveElement.relationships;
				CowRelElement element = cowElements.get(primitive.id);
				if (element != null)
				{
					return element.propertyRemoveMap;
				}
			}
			return null;
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyAddMap(NeoPrimitive primitive)
		{
			NeoPrimitiveElement primitiveElement = cowMap.get(getTransaction());
			if (primitiveElement != null && primitive is NodeImpl)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(primitive.id);
				if (element != null)
				{
					return element.propertyAddMap;
				}
			}
			else if (primitiveElement != null && primitive is RelationshipImpl)
			{
				ArrayMap<int, CowRelElement> cowElements = primitiveElement.relationships;
				CowRelElement element = cowElements.get(primitive.id);
				if (element != null)
				{
					return element.propertyAddMap;
				}
			}
			return null;
		}

		private NeoPrimitiveElement getAndSetupPrimitiveElement()
		{
			Transaction tx = getTransaction();
			if (tx == null)
			{
				throw new NotInTransactionException();
			}
			NeoPrimitiveElement primitiveElement = cowMap.get(tx);
			if (primitiveElement == null)
			{
				primitiveElement = new NeoPrimitiveElement();
				cowMap.put(tx, primitiveElement);
			}
			return primitiveElement;
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyAddMap(NeoPrimitive primitive, bool create)
		{
			if (!create)
			{
				return getCowPropertyAddMap(primitive);
			}
			NeoPrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
			if (primitive is NodeImpl)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(primitive.id);
				if (element == null)
				{
					element = new CowNodeElement();
					cowElements.put(primitive.id, element);
				}
				if (element.propertyAddMap == null)
				{
					element.propertyAddMap = new ArrayMap<int, PropertyData>();
				}
				return element.propertyAddMap;
			}
			else if (primitive is RelationshipImpl)
			{
				ArrayMap<int, CowRelElement> cowElements = primitiveElement.relationships;
				CowRelElement element = cowElements.get(primitive.id);
				if (element == null)
				{
					element = new CowRelElement();
					cowElements.put(primitive.id, element);
				}
				if (element.propertyAddMap == null)
				{
					element.propertyAddMap = new ArrayMap<int, PropertyData>();
				}
				return element.propertyAddMap;
			}
			return null;
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyRemoveMap(NeoPrimitive primitive, bool create)
		{
			if (!create)
			{
				return getCowPropertyRemoveMap(primitive);
			}
			NeoPrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
			if (primitive is NodeImpl)
			{
				ArrayMap<int, CowNodeElement> cowElements = primitiveElement.nodes;
				CowNodeElement element = cowElements.get(primitive.id);
				if (element == null)
				{
					element = new CowNodeElement();
					cowElements.put(primitive.id, element);
				}
				if (element.propertyRemoveMap == null)
				{
					element.propertyRemoveMap = new ArrayMap<int, PropertyData>();
				}
				return element.propertyRemoveMap;
			}
			else if (primitive is RelationshipImpl)
			{
				ArrayMap<int, CowRelElement> cowElements = primitiveElement.relationships;
				CowRelElement element = cowElements.get(primitive.id);
				if (element == null)
				{
					element = new CowRelElement();
					cowElements.put(primitive.id, element);
				}
				if (element.propertyRemoveMap == null)
				{
					element.propertyRemoveMap = new ArrayMap<int, PropertyData>();
				}
				return element.propertyRemoveMap;
			}
			return null;
		}

		public virtual void removeNodeFromCache(int nodeId)
		{
			if (nodeManager != null)
			{
				nodeManager.removeNodeFromCache(nodeId);
			}
		}

		public virtual void addRelationshipType(RelationshipTypeData type)
		{
			if (nodeManager != null)
			{
				nodeManager.addRelationshipType(type);
			}
		}

		public virtual void addPropertyIndex(PropertyIndexData index)
		{
			if (nodeManager != null)
			{
				nodeManager.addPropertyIndex(index);
			}
		}

		public virtual void removeRelationshipFromCache(int id)
		{
			if (nodeManager != null)
			{
				nodeManager.removeRelationshipFromCache(id);
			}
		}

		public virtual void removeRelationshipTypeFromCache(int id)
		{
			if (nodeManager != null)
			{
				nodeManager.removeRelationshipTypeFromCache(id);
			}
		}

		private class ReadOnlyTxReleaser : Synchronization
		{
			private readonly Transaction tx;

			internal ReadOnlyTxReleaser(Transaction tx)
			{
				this.tx = tx;
			}

			public virtual void afterCompletion(int status)
			{
				releaseLocks(tx);
			}

			public virtual void beforeCompletion()
			{
			}
		}

		public virtual void clearCache()
		{
			if (nodeManager != null)
			{
				nodeManager.clearCache();
			}
		}

	}
}